package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author Aupt
 * @create 2022-06-01-11:28
 */
@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter bloomFilter;



    /**
     * joinPoint.getArgs(); 获取方法参数  joinPoint.getTarget().getClass(); 获取目标类
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取目标方法对象
        Method method = signature.getMethod();
        //获取目标方法上的GmallCache注解
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);

        //获取注解对象中的缓存前缀
        String prefix = gmallCache.prefix();
        //获取方法的形参列表
        Object[] args = joinPoint.getArgs();
        //组装缓存的key
        String key = prefix + StringUtils.join(args, ",");

        //为了防止缓存穿透，使用布隆过滤器
        if (!bloomFilter.contains(key)) {
            return null;
        }

        //1.查询缓存，如果命中直接返回
        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            //把缓存数据反序列化为目标方法的返回值类型
            return JSON.parseObject(json, method.getReturnType());
        }

        //2.为了防止缓存击穿，添加分布式锁
        RLock lock = this.redissonClient.getLock(gmallCache.lock() + StringUtils.join(args, ","));
        lock.lock();

        try {
            //3.再次查询缓存，如果命中直接返回
            String json2 = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseObject(json2, method.getReturnType());
            }

            //4.执行目标方法
            Object result = joinPoint.proceed(args);

            //5.把目标方法的返回结果集放入缓存中并最终释放分布式锁
            if (result != null) {

                //为了 防止缓存雪崩，给过期时间添加随机值
                int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
                this.redisTemplate.opsForValue().set(key, JSON.toJSONString(result), timeout, TimeUnit.MINUTES);
            }

            return result;

        } finally {

            //释放分布式锁
            lock.unlock();
        }

    }


}
